using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Diagnostics;
using System.Collections;
using System.Configuration;
using AppEnvironment.Lib;
using AppEnvironment.Web;
using AppEnvironment.Lib.Interfaces;
using AppEnvironment.Lib.Internal;

namespace AppEnvironment
{

    public sealed class App : Singleton<App>
    {
        private App()
        {
            InstanceInternal = this;
            FactoryMapOnApplication = new SortedList<string, Type>();

            if (HttpContext.Current == null)
            {
                mApplication = new NonWebStateHolder();
            }
            else
            {
                mApplication = new WebApplication(HttpContext.Current.Application);
            }

            string initType = ConfigurationManager.AppSettings["AppRootInit"];
            if (!string.IsNullOrEmpty(initType))
            {
                IAppRootInit initializer = Activator.
                    CreateInstance(Type.GetType(initType, false, true)) as IAppRootInit;

                if (initializer != null)
                {
                    initializer.Init(this);
                }
            }
        }

        internal static App InstanceInternal { get; set; }

        private SortedList<string, Type> FactoryMapOnApplication { get; set; }
        private SortedList<string, Type> FactoryMapOnThread
        {
            get
            {
                bool started = false;
                SortedList<string, Type> result = ContextItems.Get("_App.FactoryMapOnThread", delegate
                {
                    started = true;
                    return CopyFactoryMap(FactoryMapOnApplication);
                });
                if (started && (OnAfterThreadStart != null))
                {
                    OnAfterThreadStart(EventArgs.Empty);
                }
                return result;
            }
            set
            {
                ContextItems["_App.FactoryMapOnThread"] = value;
            }
        }

        public event Action<EventArgs> OnAfterThreadStart = delegate { };

        public static AppCurrentThread CurrentThread
        {
            get
            {
                return ContextItems.Get<AppCurrentThread>("_AppEnvironment.App.CurrentThread", delegate 
                {
                    return new AppCurrentThread();
                });
            }
        }

        //this is not static, since it should be called once only on the instance from the AppRootInit class
        public void SetOverrideForApplication<TFromType, TToType>()
        {
            string fromType = typeof(TFromType).FullName;
            FactoryMapOnApplication[fromType] = typeof(TToType);
        }

        //this is not static, since it should be called once only on the instance from the AppRootInit class
        public void SetOverrideForApplication(Type fromType, Type toType) 
        {
            FactoryMapOnApplication[fromType.FullName] = toType;
        }

        public void SetOverrideForApplication<TFromType>(string toType)
        {
            string fromType = typeof(TFromType).FullName;

            if (!string.IsNullOrEmpty(toType))
            {
                Type toTypeValue = Type.GetType(toType, true);
                if (toTypeValue != null)
                {
                    FactoryMapOnApplication[fromType] = toTypeValue;
                }
            }
        }

        public void SetOverrideForApplication<TFromType>(Type toType)
        {
            string fromType = typeof(TFromType).FullName;
            if (toType != null)
            {
                FactoryMapOnApplication[fromType] = toType;
            }
        }

        public static void SetOverrideForThread<TFromType, TToType>()
        {
            string fromType = typeof(TFromType).FullName;
            App.InstanceProtected.FactoryMapOnThread[fromType] = typeof(TToType);
        }

        public static void SetOverrideForThread<TFromType>(string toType)
        {
            string fromType = typeof(TFromType).FullName;
            if (!string.IsNullOrEmpty(toType))
            {
                Type toTypeValue = Type.GetType(toType, true);
                if (toTypeValue != null)
                {
                    App.InstanceProtected.FactoryMapOnThread[fromType] = toTypeValue;
                }
            }
        }

        public static void SetOverrideForThread<TFromType>(Type toType)
        {
            string fromType = typeof(TFromType).FullName;
            if (toType != null)
            {
                App.InstanceProtected.FactoryMapOnThread[fromType] = toType;
            }
        }

        public static void ClearOverridesForThread()
        {
            App.InstanceProtected.FactoryMapOnThread = null;
        }

        

        private SortedList<string, Type> CopyFactoryMap(SortedList<string, Type> factoryMap)
        {
            SortedList<string, Type> result = new SortedList<string, Type>();
            foreach (var item in factoryMap)
            {
                result.Add(item.Key, item.Value);
            }
            return result;
        }

        public static IStateHolderWithExpiry Cookies
        {
            get
            {
                if (HttpContext.Current == null)
                {
                    return NonWebCookies.InstanceProtected;
                }
                else
                {
                    return WebCookies.InstanceProtected;
                }
            }
        }

        private IStateHolder mApplication;
        public static IStateHolder Application
        {
            get
            {
                return App.InstanceProtected.mApplication;
            }
        }

        internal IStateHolder GetApplication()
        {
            return mApplication;
        }

        public static IStateHolder Session
        {
            get
            {
                if (HttpContext.Current == null)
                {
                    return NonWebSession.InstanceProtected;
                }
                else
                {
                    return WebSession.InstanceProtected;
                }
            }
        }

        [ThreadStatic]
        private static IStateHolder mContextItems;
        public static IStateHolder ContextItems
        {
            get
            {
                if (HttpContext.Current == null)
                {
                    if (mContextItems == null)
                    {
                        mContextItems = new NonWebStateHolder();
                    }
                    return mContextItems;
                }
                else
                {
                    return WebContextItems.InstanceProtected;
                }
            }
        }


        /// <summary>
        /// Factory method to create new instances. If using SetOverrideFor* methods, will create overrided type,
        /// else will create new T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T New<T>()
        {
            Type requestedType = typeof(T);
            Type typeToCreate;
            if (!App.InstanceProtected.FactoryMapOnThread.TryGetValue(requestedType.FullName, out typeToCreate))
            {
                typeToCreate = requestedType;
            }            

            return (T)StateManager.GetInstance(typeToCreate, () =>
            {
                AppBeforeNewForTypeArgs<T> beforeNewArgs = new AppBeforeNewForTypeArgs<T>
                {
                    TypeToCreate = typeToCreate
                };
                CurrentThread.ForType<T>().FireOnBeforeNew(beforeNewArgs);
                if (beforeNewArgs.InstanceToUseChanged)
                {
                    return (T)beforeNewArgs.InstanceToUse; //return this instance instead
                }
                else
                {
                    object newInstance = Activator.CreateInstance(typeToCreate);
                    AppAfterNewForTypeArgs<T> afterNewArgs = new AppAfterNewForTypeArgs<T>
                    {
                        InstanceCreated = (T)newInstance
                    };
                    CurrentThread.ForType<T>().FireOnAfterNew(afterNewArgs);
                    return afterNewArgs.InstanceCreated;
                }
            });
        }

    }

}
